/* Octopus PDF - A simple document viewer for Android.
   Copyright (C) 2013  Gerald Thaler

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
package de.geraldthaler.octopuspdf.view;

import static android.opengl.GLES10.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES10.GL_DEPTH_TEST;
import static android.opengl.GLES10.GL_FASTEST;
import static android.opengl.GLES10.GL_PERSPECTIVE_CORRECTION_HINT;
import static android.opengl.GLES10.GL_PROJECTION;
import static android.opengl.GLES10.glClear;
import static android.opengl.GLES10.glClearColor;
import static android.opengl.GLES10.glDisable;
import static android.opengl.GLES10.glHint;
import static android.opengl.GLES10.glLoadIdentity;
import static android.opengl.GLES10.glMatrixMode;
import static android.opengl.GLES10.glOrthof;
import static android.opengl.GLES10.glViewport;

import java.util.ArrayList;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.os.Bundle;
import de.geraldthaler.octopuspdf.MainActivity;
import de.geraldthaler.octopuspdf.control.DocControl;
import de.geraldthaler.octopuspdf.docrender.DocRenderer;
import de.geraldthaler.octopuspdf.docrender.DocSource;
import de.geraldthaler.octopuspdf.func.Proc;
import de.geraldthaler.octopuspdf.memory.DirectByteBufferPool;
import de.geraldthaler.octopuspdf.memory.Disposable;
import de.geraldthaler.octopuspdf.util.Threading;

/**
 * @author Gerald Thaler
 */
public final class GlRenderer implements Disposable, Renderer {
    private static final int MAX_TOUCHES = 1000;
    private static final int TOUCHES_SLOWDOWN_THRESHOLD = 500;

    private final DirectByteBufferPool directByteBufferPool = new DirectByteBufferPool();
    private final DocControl docControl;
    private final DocRenderer docRenderer;
    private final FpsMeter fpsMeter = new FpsMeter();
    private int glGeneration = 0;
    private final TexturePool texturePool = new TexturePool();
    private final ArrayList<Object> sentEvents = new ArrayList<Object>();
    private final ArrayList<Object> events = new ArrayList<Object>();
    private final GLSurfaceView view;

    public GlRenderer(final MainActivity activity,  final GLSurfaceView view, final Bundle state,
    		final DocSource docSource, final int pageMode) {
        this.view = view;
        docRenderer = new DocRenderer(docSource, new Proc() {
            @Override
            public void invoke() {
                view.requestRender();
            }
        });
        docRenderer.start();
        final Bundle docControlState;
        if (state == null)
            docControlState = null;
        else
            docControlState = state.getBundle("docControl");
        docControl = new DocControl(activity, texturePool, directByteBufferPool, docRenderer,
        		fpsMeter, docControlState, pageMode);
    }
    
    @Override
    public void dispose() {
        docControl.dispose();
        docRenderer.dispose();
    }

    @Override
    public void onDrawFrame(final GL10 glUnused) {
        fpsMeter.frame();
        docRenderer.invokeCallbacks();
        synchronized (sentEvents) {
            events.addAll(sentEvents);
            sentEvents.clear();
        }
        for (final Object event: events)
            docControl.onEvent(event);
        events.clear();
        glClear(GL_COLOR_BUFFER_BIT);
        if (docControl.onDrawFrame())
            view.requestRender();
        else
            fpsMeter.stop();
    }

    @Override
    public void onSurfaceChanged(final GL10 glUnused, final int width, final int height) {
        docRenderer.setPixmapMemoryThreshold(width * height);
        docControl.onSurfaceChanged(width, height);
        glViewport(0, 0, width, height);
        // orthogonal projection in pixel units, left handed, y-axis pointing down:
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        final float w = (int) (width * 0.5f);
        final float h = (int) (height * 0.5f);
        glOrthof(-w, w, h, -h, 1f, -1f);
    }

    @Override
    public void onSurfaceCreated(final GL10 glUnused, final EGLConfig config) {
        ++glGeneration;
        texturePool.clear(glGeneration);
        glDisable(GL_DEPTH_TEST);
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
        glClearColor(0f, 0f, 0f, 1f);
        docControl.onSurfaceCreated(glGeneration);
    }

    public void saveInstanceState(final Bundle state) {
        final Bundle docControlState = new Bundle();
        docControl.saveInstanceState(docControlState);
        state.putParcelable("docControl", docControlState);
    }

    public void sendEvent(final Object event) {
        final boolean slowdown;
        synchronized (sentEvents) {
            final int count = sentEvents.size();
            slowdown = count >= TOUCHES_SLOWDOWN_THRESHOLD;
            if (count < MAX_TOUCHES)
                sentEvents.add(event);
            if (count == 0)
                view.requestRender();
        }
        if (slowdown)
            Threading.sleepUninterruptibly(500);
    }
}
